// @requires Cocktail, Ingredient, Good

CocktailsPage =
{
	init: function (states, nodes, styles, cookies) {
		this.view       = new CocktailsView(states, nodes, styles)
		this.model      = new CocktailsModel(states, this.view)
		this.controller = new CocktailsController(states, cookies, this.model, this.view)
	}
}

$.onready(
	function () {
		var nodes = {
			bodyWrapper: $$('#main-wrapper .body-wrapper')[0],
			resultsDisplay: $('results_display'),
			resultsRoot: $('surface'),
			pagerRoot: $('p-list'),
			
			bigNext: $$(".pager-big .next")[0],
			bigPrev: $$(".pager-big .prev")[0],
			
			alphabetRu: $('alphabetical-ru'),
			lettersAll: $('letters_all'),
			
			tagsList: $('tags_list'),
			strengthsList: $('strengths_list'),
			methodsList: $('methods_list'),
			
			searchByName: $('search_by_name'),
			searchByIngreds: $('search_by_ingreds'),
			searchByIngredsInput: $$('#search_by_ingreds input')[0],
			searchByIngredsForm: $$('#search_by_ingreds form')[0],
			searchByLetter: $('search_by_letter'),
			
			tagStrengthArea: $('b_search'),
			mainArea: $('b_content'),
			
			searchTabs: $('search_tabs'),
			ingredsView: $$(".ingreds-list")[0],
			removeAllIngreds: $$(".ingreds-list .rem")[0],
			searchesList: $('ingredients_list'),
			searchTips: $('search_tips'),
			
			ingredientsLink: $('all_list'),
			
			searchExampleIngredient: $('search_example_ingredient'),
			searchTipIngredient: $('search_tip_ingredient'),
			
			searchExampleName: $('search_example_name'),
			searchExampleNameEng: $('search_example_name_eng'),
			searchTipName: $('search_tip_name'),
			
			cartEmpty: $('cart_draghere'),
			cartFull: $('cart_contents'),
			
			spotlighted: $('spotlighted')
		}
		
		var styles = {
			selected: 'selected-button',
			disabled: 'disabled',
			point: 'point'
		}
		
		var cookies = {
			filter: 'filters',
			force: 'force',
			
			strengthState: 'strength_state',
			tagState: 'tag_state',
			methodState: 'method_state'
		}
		
		var states = {
			byName:        0,
			byLetter:      1,
			byIngredients: 2,
			
			defaultState:  0
		}
		
		CocktailsPage.init(states, nodes, styles, cookies)
		Calculator.init()
		Theme.bind()
	}
)

if (/WebKit\/|MSIE [6789]\./.test(navigator.userAgent))
(function(){

var status = {}

document.addEventListener
(
	'keydown',
	function (e)
	{
		var target = e.target
		
		var ne = document.createEvent('Event')
		ne.initEvent('keypress', true, true)
		
		// copying valueable data
		ne.altKey = e.altKey
		ne.ctrlKey = e.ctrlKey
		ne.metaKey = e.metaKey
		ne.charCode = e.charCode
		ne.keyCode = e.keyCode
		
		if (status[e.keyCode])
			e.stopPropagation()
		else
			status[e.keyCode] = true
		
		if (!target.dispatchEvent(ne))
			e.preventDefault()
	},
	true
)

document.addEventListener
(
	'keyup',
	function (e)
	{
		status[e.keyCode] = false
	},
	true
)

})();
;(function(){

var rex = /([\\\.\*\+\?\$\^\|\(\)\[\]\{\}])/g

if (!RegExp.escape)
RegExp.escape = function (str)
{
	return ('' + str).replace(rex, '\\$1')
}

})();

Switcher =
{
	bind: function (main, buttons, tabs, names)
	{
		if (!main || !buttons || !tabs)
			throw new Error('main, buttons or tabs are not defined: ' + [!!main, !!buttons, !!tabs].join(', '))
		main.nodes = {buttons: Array.copy(buttons), tabs: Array.copy(tabs)}
		main.names = names || []
		
		main.onselect = function () {}
		main.setTabs = function (tabs) { this.nodes.tabs = tabs }
		main.setNames = function (names) { this.names = names }
		main.select = function (num)
		{
			if (typeof num != 'number')
				num = this.names.indexOf(num)
			
			if (num < 0 || this.onselect(num) === false)
				return
			
			this.drawSelected(num)
		}
		main.drawSelected = function (num)
		{
			if (typeof num != 'number')
				num = this.names.indexOf(num)
			
			if (num < 0)
				return
			
			var buttons = this.nodes.buttons
			for (var i = 0; i < buttons.length; i++)
				if (buttons[i])
					num == i ? buttons[i].addClassName('selected') : buttons[i].removeClassName('selected')
			
			var tabs = this.nodes.tabs
			if (tabs && tabs[num])
				for (var i = 0; i < tabs.length; i++)
					if (tabs[i])
						num == i ? tabs[i].show() : tabs[i].hide()
		}
		
		function isParent (node, parent, root)
		{
			do
			{
				// log(node)
				if (node == parent)
					return true
				if (node == root)
					return false
			}
			while (node = node.parentNode)
			
			return false
		}
		
		function mouseSelect (e)
		{
			var buttons = this.nodes.buttons
			var num = -1
			for (var i = 0; i < buttons.length; i++)
				if (isParent(e.target, buttons[i], this))
					num = i
			
			return this.select(num)
		}
		main.addEventListener('mousedown', mouseSelect, false)
		
		return main
	}
}

;(function(){

var doc = document, undef, myName = 'NodesShortcut'

function T (text)
{
	return doc.createTextNode(text)
}

function N (tag)
{
	return doc.createElement(tag)
}

function Nc (tag, cn)
{
	var node = doc.createElement(tag)
	node.className = cn
	return node
}

function Nct (tag, cn, text)
{
	var node = doc.createElement(tag)
	node.className = cn
	node.appendChild(doc.createTextNode(text))
	return node
}


function E (tag, cn, props)
{
	var node = doc.createElement(tag)
	if (cn !== undef) node.className = cn
	if (props)
		for (var i in props)
			node.setAttribute(i, props[i])
	return node
}

var Me = self[myName] = {}
	funcs = Me.funcs = {T: T, N: N, Nc: Nc, Nct: Nct, E: E}

var pairs = []
for (var k in funcs)
	pairs.push(k + '=' + myName + '.funcs.' + k)

Me.code = 'var ' + pairs.join(',')
Me.include = function () { return this.code }


})();

;(function(){

var myName = 'MVC'

function Me () {}

Me.prototype =
{
	__mvc_interlink: function ()
	{
		var constructor = this.constructor,
			model = this.model = new constructor.Model(),
			view = this.view = new constructor.View(),
			controller = this.controller = new constructor.Controller()
		
		model.view = controller.view = view
		view.controller = controller
		controller.model = model
		model.parent = view.parent = controller.parent = this
	},
	
	initialize: function () {}
}

self[myName] = Me
Me.className = myName

function Model () {}
Model.prototype.initialize = function () {}
Model.className = myName + '.Model'
Me.Model = Model

function View () {}
View.prototype.initialize = function () {}
View.className = myName + '.View'
Me.View = View

function Controller () {}
Controller.prototype.initialize = function () {}
Controller.className = myName + '.Controller'
Me.Controller = Controller


Me.setup = function (klass, name, parent)
{
	if (!parent)
		parent = Me
	
	klass.prototype = new parent()
	klass.className = name
	
	
	function Model ()
	{
		this.constructor = Model
		this.initialize.apply(this, arguments)
	}
	Model.className = name + '.Model'
	Model.prototype = new parent.Model()
	klass.Model = Model
	
	
	function View ()
	{
		this.constructor = View
		this.initialize.apply(this, arguments)
	}
	View.className = name + '.View'
	View.prototype = new parent.View()
	klass.View = View
	
	
	function Controller ()
	{
		this.constructor = Controller
		this.initialize.apply(this, arguments)
	}
	Controller.className = name + '.Controller'
	Controller.prototype = new parent.Controller()
	klass.Controller = Controller
	
	
	return klass
}

Me.create = function (name, parent)
{
	function klass ()
	{
		this.constructor = klass
		this.__mvc_interlink()
		this.initialize()
	}
	
	return this.setup(klass, name, parent)
}

})();

;(function(){

var Papa

;(function(){

var myName = 'Autocompleter',
	Me = Papa = self[myName] = MVC.create(myName)

var myProto =
{
	initialize: function ()
	{
		this.model.initialize()
		this.view.initialize()
		this.controller.initialize()
		this.model.controller = this.controller
	},
	
	bind: function (main, count)
	{
		this.view.bind({main:main})
		this.setCount(count === undefined ? 15 : count)
		return this
	},
	
	setDataSource: function (ds) { this.model.dataSource = ds },
	setValueGetter: function (f) { this.view.valueGetter = f },
	setValueSetter: function (f) { this.view.valueSetter = f },
	setCount: function (v) { this.model.setCount(v); this.view.setCount(v) },
	setInstant: function (v) { this.controller.instant = v },
	onconfirm: function () {}
}

Object.extend(Me.prototype, myProto)

})();


;(function(){

var Me = Papa.View

eval(NodesShortcut.include())

var myProto =
{
	valueGetter: function (node) { return node.value },
	valueSetter: function (node, value) { node.value = value },
	
	initialize: function ()
	{
		this.nodes = {}
		this.keyMap = {38:'goUp', 40:'goDown', 37:false, 39:false, 9:false, 16:false, 17:false, 18:false, 91:false, 13:'goEnter', 27:'goEscape'}
	},
	
	bind: function (nodes)
	{
		this.nodes = nodes
		var main = nodes.main
		main.setAttribute('autocomplete', 'off')
		
		var list = this.nodes.list = N('ul')
		list.className = 'autocomplete'
		main.parentNode.appendChild(list)
		
		var me = this
		main.addEventListener('blur', function (e) { me.onBlur(e) }, false)
		main.addEventListener('keypress', function (e) { me.onKeyPress(e) }, false)
	},
	
	onKeyPress: function (e)
	{
		var targ = e.target, controller = this.controller,
			action = this.keyMap[e.keyCode]
		// alert(e.keyCode)
		if (action === false)
			return
		else if (action)
		{
			if (controller[action](targ.value) === false)
			{
				e.preventDefault()
				e.stopPropagation()
			}
		}
		else
		{
			var me = this
			setTimeout(function () { me.onValue(targ) }, 1)
		}
	},
	
	onValue: function (node)
	{
		this.controller.goValue(this.valueGetter(node))
	},
	
	onBlur: function (e)
	{
		this.controller.goBlur()
	},
	
	onMouseMove: function (node, e)
	{
		this.controller.itemHovered(node.num)
	},
	
	onMouseDown: function (node, e)
	{
		this.controller.itemClicked(node.num)
	},
	
	setCount: function (count)
	{
		this.createItemsNodes(count)
	},
	
	renderVariant: function (str)
	{
		this.valueSetter(this.nodes.main, str)
	},
	
	show: function ()
	{
		var nodes = this.nodes
		nodes.main.addClassName('autocompleting')
		nodes.list.show()
		this.active = true
	},
	
	hide: function ()
	{
		var nodes = this.nodes
		nodes.main.removeClassName('autocompleting')
		nodes.list.hide()
		this.active = false
	},
	
	createItemsNodes: function (count)
	{
		var list = this.nodes.list, items = this.nodes.items = []
		list.empty()
		
		var me = this
		function mousedown (e) { me.onMouseDown(this, e) }
		function mousemove (e) { me.onMouseMove(this, e) }
		
		for (var i = 0; i < count; i++)
		{
			var item = items[i] = N('li')
			item.className = 'item'
			item.hide()
			list.appendChild(item)
			item.num = i
			item.addEventListener('mousedown', mousedown, false)
			item.addEventListener('mousemove', mousemove, false)
		}
	},
	
	renderResults: function (results)
	{
		var items = this.nodes.items
		for (var i = 0; i < results.length && i < items.length; i++)
		{
			var r = results[i],
				item = items[i]
			item.empty()
			item.appendChild(r[1]) // [1] means a text representing node (or DocumentFragment)
			item.show()
		}
		
		for (; i < items.length; i++)
			items[i].hide()
	},
	
	selectItem: function (num)
	{
		if (this.selected === num)
			return
		
		var node, items = this.nodes.items
		
		if ((node = items[this.selected]))
			node.removeClassName('selected')
		
		if ((node = items[num]))
			node.addClassName('selected')
		
		this.selected = num
	}
}

Object.extend(Me.prototype, myProto)

})();


;(function(){

var Me = Papa.Controller

var myProto =
{
	initialize: function ()
	{
		this.reset()
		this.value = undefined
	},
	
	reset: function ()
	{
		this.results = []
		this.selected = -1
		this.value = ''
	},
	
	begin: function ()
	{
		if (this.active)
			return
		// log('begin')
		
		this.active = true
		this.view.show()
	},
	
	end: function ()
	{
		if (!this.active)
			return
		// log('end')
		
		this.active = false
		this.reset()
		this.view.hide()
	},
	
	search: function ()
	{
		this.model.search(this.value)
	},
	
	setResults: function (results)
	{
		this.selected = -1
		this.results = results
		this.view.renderResults(results)
		this.view.selectItem(-1)
	},
	
	selectBy: function (dir)
	{
		var total = this.results.length,
			selected = this.selected
		
		selected += dir
		
		if (selected < -1)
			selected = total - 1
		else if (selected >= total)
			selected = -1
		
		this.select(selected)
	},
	
	select: function (num)
	{
		if (this.selected === num)
			return
		
		this.selected = num
		this.view.selectItem(num)
	},
	
	sendSelected: function ()
	{
		this.view.renderVariant(this.selectedValue())
	},
	
	selectedValue: function ()
	{
		var selected = this.selected
		return selected < 0 ? this.value : this.results[selected][0] // [0] means a text value
	},
	
	dispatchConfirm: function ()
	{
		return this.parent.onconfirm({type:'confirm', data: {value:this.selectedValue(), selected:this.selected, results:this.results}})
	},
	
	goValue: function (value)
	{
		if (this.value !== value)
		{
			this.value = value
			if (value !== '')
			{
				this.begin()
				this.search()
			}
			else
				this.end()
		}
	},
	
	goUp: function (value)
	{
		if (this.active)
		{
			this.selectBy(-1)
			this.sendSelected()
			return false // drop an event
		}
	},
	
	goDown: function (value)
	{
		if (this.active)
		{
			this.selectBy(1)
			this.sendSelected()
		}
		else
		{
			this.value = value
			if (value !== '')
			{
				this.begin()
				this.search()
			}
		}
		
		return false // drop an event
	},
	
	goEnter: function (value)
	{
		if (this.active)
		{
			if (this.dispatchConfirm() !== false)
				this.sendSelected()
			
			this.end()
			
			return this.instant || false
		}
	},
	
	goEscape: function (value)
	{
		if (this.active)
		{
			this.view.renderVariant(this.value)
			this.end()
		}
	},
	
	goBlur: function ()
	{
		if (this.active)
		{
			this.sendSelected()
			this.end()
		}
	},
	
	itemHovered: function (num)
	{
		this.select(num)
	},
	
	itemClicked: function (num)
	{
		this.select(num)
		if (this.dispatchConfirm() !== false)
			this.sendSelected()
		this.end()
	}
}

Object.extend(Me.prototype, myProto)

})();


;(function(){

var Me = Papa.Model

var myProto =
{
	setCount: function (v) { this.count = v },
	search: function (value)
	{
		var ds = this.dataSource
		this.controller.setResults(ds ? ds.search(value, this.count) : [])
	}
}

Object.extend(Me.prototype, myProto)

})();


})();

function CocktailsModel (states, view) {
	this.resultSet = [];
	
	this.filters = {
		name:        "",
		letter:      "",
		tag:         "",
		strength:    "",
		ingredients: [],
		page:        0,
		state:       states.defaultState
	};
	
	this.resultSet = [];
	
	
	this.initialize = function(filters) {
		this.filters = this.completeFilters(filters);
		var viewData = {}
		
		viewData.ingredients = Ingredient.getAllNames()
		viewData.tags = Cocktail.getGroups()
		viewData.strengths = Cocktail.getStrengths()
		viewData.methods = Cocktail.getMethods()
		
		viewData.letters = Cocktail.getFirstLetters()
		viewData.names = Ingredient.getAllSecondNames()
		viewData.byName = Ingredient.getNameBySecondNameHash()
		view.initialize(viewData, this.filters.state);
		this.applyFilters();
	};
	
	this.randomIngredient = function(){
		var allNames = Ingredient.getAllNames()
		var num = Math.floor((allNames.length)*Math.random());
		return allNames[num];
	};
	
	this.randomCocktailNames = function(){
		var cocktails = Cocktail.getAll()
		var num = Math.floor((cocktails.length)*Math.random());
		var cocktail = cocktails[num];
		return [cocktail.name, cocktail.name_eng];
	};
	
	this.completeFilters = function(filters){
		if(!filters)             filters = {};
		if(!filters.name)        filters.name = "";
		if(!filters.letter)      filters.letter = "";
		if(!filters.tag)         filters.tag = "";
		if(!filters.strength)    filters.strength = "";
		if(!filters.method)      filters.method = "";
		if(!filters.page)        filters.page = 0;
		
		if(!filters.ingredients) filters.ingredients = [];
		else if(filters.ingredients.split) filters.ingredients = filters.ingredients.split(",");
		
		if (!filters.marks)
			filters.marks = []
		else if (filters.marks.split)
			filters.marks = filters.marks.split(',')
		
		if(!filters.state) filters.state = states.defaultState;
		
		if(filters.ingredients.length || filters.tag || filters.strength || filters.method) {
			filters.state = states.byIngredients;
		}
		
		return filters;
	};
	
	this.resetFilters = function(){
		this.filters.name = "";
		this.filters.letter = "";
		this.filters.tag = "";
		this.filters.strength = "";
		this.filters.method = "";
		this.filters.ingredients = [];
		this.filters.marks = []
		this.filters.page = 0;
		this.filters.state = states.defaultState;
	};
	
	this.filtersAreEmpty = function(){
		return (!this.filters.name && !this.filters.letter &&
				!this.filters.tag && !this.filters.strength &&
				!this.filters.method && !this.filters.ingredients.length)
	};
	
	
	this.uniqueTags = function(set){
		var res = [];
		for(var i = 0; i < set.length; i++){ res = res.concat(set[i].groups) }
		return res.uniq();
	};
	
	this.uniqueStrengths = function(set){
		var res = [];
		for(var i = 0; i < set.length; i++){ res.push(set[i].strength) }
		return res.uniq();
	};

	this.uniqueMethods = function(set){
		var res = [];
		for(var i = 0; i < set.length; i++){ res.push(set[i].method) }
		return res.uniq();
	};

	this.onStateChanged = function(state){
		this.resetFilters();
		this.filters.state = state;
		this.applyFilters();
	}
	
	this.onPageChanged = function(num){
		this.filters.page = num;
		view.controller.saveFilters(this.filters);
	};
	
	this.onLetterFilter = function(name, name_all) {
		if(name != this.filters.letter) {
			this.filters.ingredients = [];
			this.filters.tag         = "";
			this.filters.strength    = "";
			this.filters.method      = "";
			this.filters.page        = 0;
			
			if(name != name_all) {
				this.filters.letter    = name;
				Statistics.cocktailsFilterSelected(name)
			} else this.filters.letter = "";
			this.applyFilters();
		}
	};
	
	this.onNameFilter = function(name) {
		if(name != this.filters.name) {
			this.filters.ingredients = [];
			this.filters.tag         = "";
			this.filters.strength    = "";
			this.filters.method      = "";
			this.filters.page        = 0;
			this.filters.name        = name;
			this.applyFilters();
		}
	}
	
	this.onTagFilter = function(name) {
		if(name != this.filters.tag) {
			this.filters.letter  = "";
			this.filters.tag     = name;
			Statistics.cocktailsFilterSelected(name)
		} else this.filters.tag  = "";
		this.filters.method = "";
		this.filters.page = 0;
		this.applyFilters();
	};
	
	this.onStrengthFilter = function(name) {
		if(name != this.filters.strength) {
			this.filters.letter      = "";
			this.filters.strength    = name;
			Statistics.cocktailsFilterSelected(name)
		} else this.filters.strength = "";
		this.filters.page = 0;
		this.filters.tag = "";
		this.filters.method = "";
		this.applyFilters();
	};
	
	this.onMethodFilter = function(name) {
		if(name != this.filters.method) {
			this.filters.letter  = "";
			this.filters.method  = name;
			Statistics.cocktailsFilterSelected(name)
		} else this.filters.method = "";
		this.filters.page = 0;
		this.applyFilters();
	};	

	this.onIngredientFilter = function(name, remove) {
		this.filters.letter   = "";
		this.filters.page     = 0;
		this.filters.strength = "";
		this.filters.tag      = "";
		this.filters.method   = "";
		
		if (!name) // removing all
		{
			this.filters.ingredients = []
			this.filters.marks = []
			this.applyFilters()
			return
		}
		
		var idx = this.filters.marks.indexOf(name)
		if (idx >= 0)
		{
			this.filters.marks.splice(idx, 1)
			this.applyFilters()
			return
		}
		
		var ingredient = Ingredient.getByNameCI(name)
		if (!ingredient)
			return
		
		var idx = this.filters.ingredients.indexOf(ingredient.name);
		if (remove) {
			this.filters.ingredients.splice(idx, 1);
		} else if (idx == -1){
			this.filters.ingredients.push(ingredient.name);
			Statistics.ingredientTypedIn(ingredient)
		} else return; // duplicate entry
		this.applyFilters();
	};
	
	this.onMarkAddFilter = function (name)
	{
		var idx = this.filters.marks.indexOf(name)
		if (idx < 0)
		{
			this.filters.marks.push(name)
			this.applyFilters()
			return
		}
	}
	
	// get states by current filters
	this.getGroupStates = function(){
		var set = [], groupStates = {};
		
		if (this.filtersAreEmpty())
		{
			var res = {}
			res.tags = Cocktail.getGroups()
			res.strengths = Cocktail.getStrengths()
			res.methods = Cocktail.getMethods()
		}
		
		// strengths state - depends only on ingredients
		var rFilters = cloneObject(this.filters);
		rFilters.strength = "", rFilters.tag  = "", rFilters.method = "";
		groupStates.strengths = this.uniqueStrengths(this.getCocktailsByFilters(rFilters, states));
		
		// tags state - depends on ingredients and strength
		rFilters = cloneObject(this.filters);
		rFilters.tag = "", rFilters.method = "";
		groupStates.tags = this.uniqueTags(this.getCocktailsByFilters(rFilters, states));
		
		// methods state - depends on ingredients, strength and tag
		rFilters = cloneObject(this.filters);
		rFilters.method = "";
		groupStates.methods = this.uniqueMethods(this.getCocktailsByFilters(rFilters, states));
		
		return groupStates;
	};
	
	var getBySimilarNameCache = {},
		allCocktails = Cocktail.getAll()
	this.getBySimilarName = function (name)
	{
		if (getBySimilarNameCache[name])
			return getBySimilarNameCache[name]
			
		var words = name.split(/\s+/),
			res = [], db = allCocktails
		
		for (var i = 0; i < words.length; i++)
			words[i] = new RegExp('(?:^|\\s|-)' + RegExp.escape(words[i]), 'i')
		
		var first = words[0], jl = words.length
		SEARCH: for (var i = 0; i < db.length; i++)
		{
			var cocktail = db[i], name
			
			if (first.test(cocktail.name))
				name = cocktail.name
			else if (first.test(cocktail.name_eng))
				name = cocktail.name_eng
			else
				continue SEARCH
			
			for (var j = 1; j < jl; j++)
				if (!words[j].test(name))
					continue SEARCH
			
			res.push(cocktail)
		}
		return (getBySimilarNameCache[name] = res)
	},
	
	
	this.getCocktailsByFilters = function (filters, states)
	{
		var res = null
		
		if (filters.name)
			return this.getBySimilarName(filters.name)
		
		if (filters.letter)
			return Cocktail.getByLetter(filters.letter)
		
		if (filters.tag)
			res = Cocktail.getByGroup(filters.tag)
		
		if (filters.strength)
			res = Cocktail.getByStrength(filters.strength, res)
		
		if (filters.method)
			res = Cocktail.getByMethod(filters.method, res)
		
		if (filters.marks && filters.marks.length)
		{
			var marks = filters.marks, ingredients = []
			for (var i = 0; i < marks.length; i++)
				ingredients.push(Ingredient.getByMark(marks[i]))
			
			// concat all the ingredients in one native operation just like SIMD ;)
			ingredients = Array.prototype.concat.apply([], ingredients)
			res = Cocktail.getByIngredients(ingredients, {db: res, count: 1})
		}
		
		if (filters.ingredients && filters.ingredients.length)
			res = Cocktail.getByIngredientNames(filters.ingredients, {db: res})
		
		if (!res)
		{
			if (filters.state == states.byName)
				res = Cocktail.getAll().shuffled()
			else if (filters.state == states.byIngredients)
				res = Cocktail.getAll().sortedBy(Cocktail.complexitySort)
			else
				res = Cocktail.getAll().sortedBy(Cocktail.nameSort)
		}
		
		return res
	}
	
	
	this.applyFilters = function()
	{
		var filters = this.filters
		view.onModelChanged(this.getCocktailsByFilters(filters, states), filters, this.getGroupStates());
	};
}

;(function(){

var myName = 'IngredientsSearcher'

function Me (ingredients, names)
{
	this.ingredients = ingredients || []
	this.names = names || {}
	this.cache = {}
	this.duplicates = {}
	this.favorites = {}
}

eval(NodesShortcut.include())

Me.prototype =
{
	search: function (substr, count)
	{
		substr = ('' + substr).trim()
		if (substr === '')
			return []
		
		var parts = substr.split(/ +/)
		
		var cache = this.cache
		var rows = cache[substr]
		if (!rows)
			rows = cache[substr] = this.searchInSet(this.ingredients, parts)
		
		var duplicates = this.duplicates, favorites = this.favorites,
			filtered = []
		
		for (var i = 0, il = rows.length; i < il; i ++)
		{
			var row = rows[i]
			if (duplicates[row[1]])
				row[0] *= 100000
			else if (favorites[row[1]])
				row[0] *= 0.000001
		}
		
		rows.sort(this.sortByWeight)
		
		var res = []
		for (var i = 0, il = rows.length; i < il && i < count; i++)
			res[i] = rows[i][1]
		
		return this.renderRows(res, parts, this.names, this.duplicates)
	},
	
	searchInSet: function (set, parts)
	{
		parts = parts.slice()
		// first search for the most lengthy part
		parts.sort(this.sortByLength)
		
		// lightweight rex for initial filtering
		var filter = new RegExp('(?:^|[ \\-])' + RegExp.escape(parts[0]), 'i')
		
		var rexes = []
		for (var i = 0, il = parts.length; i < il; i++)
			rexes[i] = new RegExp('(^|.*[ \\-])(' + RegExp.escape(parts[i]) + '[^ \\-]*)', 'i')
		
		
		var rows = [], rl = rexes.length
		set: for (var i = 0, il = set.length; i < il; i++)
		{
			var v = set[i]
			
			if (!filter.test(v))
				continue set
			
			var weight = 0
			for (var j = 0; j < rl; j++)
			{
				var m = rexes[j].exec(v)
				if (!m)
					continue set
				
				// weight += (10000 * m[2].length) + (100 * m[1].length) + v.length
				weight += (10000 * m[2].length) + m[1].length
			}
			
			rows.push([weight, v])
		}
		
		return rows
	},
	
	renderRows: function (values, parts, names, duplicates)
	{
		var res = []
		
		var rexes = []
		for (var i = 0, il = parts.length; i < il; i++)
		{
			var p = parts[i]
			var rex = rexes[i] = new RegExp('(?:^|.*[ \\-])(' + RegExp.escape(p) + '[^ \\-]*)', 'gi')
			rex.part = p
		}
		
		for (var i = 0, il = values.length; i < il; i++)
		{
			var v = values[i]
			
			
			// find all sun-matches
			var matches = []
			for (var j = 0, jl = rexes.length; j < jl; j++)
			{
				var rex = rexes[j]
				
				rex.lastIndex = 0
				var m = rex.exec(v)
				if (!m)
					continue
				
				var begin = rex.lastIndex - m[1].length
				matches.push([begin, begin + rex.part.length])
			}
			
			
			// sort sub-matches so as the nearest left and biggest will be first
			function byPositionAndLength (a, b)
			{
				return a[0] - b[0] || (b[1] - b[0]) - (a[1] - a[0])
			}
			
			matches.sort(byPositionAndLength)
			
			
			// filt-out overlays
			var last = 0, filtered = []
			for (var j = 0, jl = matches.length; j < jl; j++)
			{
				var m = matches[j]
				if (m[0] < last)
					continue
				
				filtered.push(m)
				last = m[1]
			}
			matches = filtered
			
			
			// split value into row of nodes
			var text = N('span')
			
			var pos = 0
			for (var j = 0, jl = matches.length; j < jl; j++)
			{
				var m = matches[j],
					begin = m[0],
					end = m[1]
				
				if (pos != begin)
					text.appendChild(T(v.substring(pos, begin)))
				
				text.appendChild(Nct('span', 'substr', v.substring(begin, end)))
				
				pos = end
			}
			
			if (pos < v.length)
				text.appendChild(T(v.substring(pos)))
			
			var value = v
			
			var name = names[v]
			if (name)
			{
				text.appendChild(T(' — это «' + name + '»'))
				value = name
			}
			
			var duplicate = duplicates[v]
			if (duplicate)
			{
				if (duplicate === true)
					text.appendChild(Nct('span', 'hint', ' (уже выбрано)'))
				else
					text.appendChild(Nct('span', 'hint', ' (уже в группе «' + duplicate + '»)'))
				
				text.className += ' duplicate'
			}
			
			res[i] = [value, text]
		}
		
		return res
	},
	
	sortByWeight: function (a, b) { return a[0] - b[0] },
	sortByLength: function (a, b) { return b.length - a.length }
}

Me.className = myName
self[Me.className] = Me

})();
function remClass(elem, className) { if(elem) elem.removeClassName(className) };
function setVisible (elem, b) { b ? elem.show() : elem.hide() }

function CocktailsView (states, nodes, styles) {
	
	new RollingImagesLite(nodes.resultsDisplay, {animationType: 'easeInOutQuad', duration:0.75});
	
	this.filterElems   = { tag: null, strength: null, method: null, letter: null };
	this.perPage       = 16;
	this.np            = -1;
	this.renderedPages = {}
	this.nodeCache     = []
	
	
	this.riJustInited  = true;
	this.dropTargets   = [nodes.cartEmpty, nodes.cartFull];
	
	this.currentState;
	this.currentFilters;
	this.stateSwitcher;
	this.resultSet; // for caching purposes only
	
	this.initialize = function (viewData, state)
	{
		this.viewData = viewData
		
		var set = viewData.ingredients.slice()
		set.push.apply(set, viewData.names)
		set = set.sort()
		
		var searcher = this.searcher = new IngredientsSearcher(set, viewData.byName)
		var completer = this.completer = new Autocompleter().bind(nodes.searchByIngredsInput)
		completer.setDataSource(searcher)
		
		this.renderLetters(nodes.alphabetRu,     this.viewData.letters);
		this.renderGroupSet(nodes.tagsList,      this.viewData.tags);
		this.renderGroupSet(nodes.strengthsList, this.viewData.strengths);
		this.renderGroupSet(nodes.methodsList,   this.viewData.methods);
		
		this.bindEvents();
		this.turnToState(state);
	};
	
	this.bindEvents = function () {
		var self = this;
		
		var letterLinks = $$("a", nodes.alphabetRu).concat(nodes.lettersAll);
		for(var i = 0; i < letterLinks.length; i++){
			letterLinks[i].addEventListener('mousedown', function(e){
				self.controller.onLetterFilter(e.target.innerHTML.toUpperCase(), 
											nodes.lettersAll.innerHTML.toUpperCase());
			}, false);
		}
		
		var tagLinks = $$("dd", nodes.tagsList);
		for(var i = 0; i < tagLinks.length; i++){
			tagLinks[i].addEventListener('mousedown', function(num){ return function(){
				if(!tagLinks[num].hasClassName(styles.disabled)) {
					self.controller.onTagFilter(this.value)
				}
			}}(i), false);
		}
		
		var strengthLinks = $$("dd", nodes.strengthsList);
		for(var i = 0; i < strengthLinks.length; i++){
			strengthLinks[i].addEventListener('mousedown', function(num){ return function(){
				if(!strengthLinks[num].hasClassName(styles.disabled)) {
					self.controller.onStrengthFilter(this.innerHTML.toLowerCase());
				}
			}}(i), false);
		}

		var methodLinks = $$("dd", nodes.methodsList);
		for(var i = 0; i < methodLinks.length; i++){
			methodLinks[i].addEventListener('mousedown', function(num){ return function(){
				if(!methodLinks[num].hasClassName(styles.disabled)) {
					self.controller.onMethodFilter(this.innerHTML.toLowerCase());
				}
			}}(i), false);
		}
		
		var ril = nodes.resultsDisplay.RollingImagesLite;
		
		nodes.bigPrev.addEventListener('mousedown', function(e){ ril.goPrev() }, false);
		nodes.bigNext.addEventListener('mousedown', function(e){ ril.goNext() }, false);
		
		ril.onselect = function (node, num) {
			if (!self.riJustInited) {
				self.controller.onPageChanged(num);
				self.renderNearbyPages(num, 0)
			} else { self.riJustInited = false }
			
			// big pager buttons
			if(num == (self.np-1) || self.np == 1) nodes.bigNext.addClassName(styles.disabled);
			else nodes.bigNext.removeClassName(styles.disabled);
			if(num == 0 || self.np == 1) nodes.bigPrev.addClassName(styles.disabled);
			else nodes.bigPrev.removeClassName(styles.disabled);
		}
		
		nodes.searchExampleIngredient.addEventListener('mousedown', function(e){ self.onIngredientAdded(this.innerHTML) }, false);
		
		nodes.searchByName.getElementsByTagName("form")[0].addEventListener('submit', function(e) { e.preventDefault() }, false);
		var searchByNameInput = nodes.searchByName.getElementsByTagName("input")[0];
		searchByNameInput.addEventListener('keyup', function(e){ self.controller.onNameFilter(this.value) }, false);
		
		nodes.searchTipName.realShow = nodes.searchTipName.show
		nodes.searchTipName.show = function () {
			this.realShow()
			var names = self.controller.needRandomCocktailNames();
			nodes.searchExampleName.innerHTML = names[0];
			nodes.searchExampleNameEng.innerHTML = names[1];
		};
		
		nodes.removeAllIngreds.addEventListener('click', function(e){
				self.onAllIngredientsRemoved();
			}, false);
		
		nodes.searchTipIngredient.realShow = nodes.searchTipIngredient.show
		nodes.searchTipIngredient.show = function () {
			this.realShow()
			nodes.searchExampleIngredient.innerHTML = self.controller.needRandomIngredient();
		};
		
		nodes.ingredsView.realShow = nodes.ingredsView.show
		nodes.ingredsView.show = function(){
			this.realShow()
			nodes.searchTips.hide();
		}
		
		nodes.ingredsView.realHide = nodes.ingredsView.hide
		nodes.ingredsView.hide = function(){
			this.realHide()
			nodes.searchTips.show();
		}
		
		var nameSearchHandler = function (e) {
			searchByNameInput.value = this.innerHTML;
			self.controller.onNameFilter(this.innerHTML);
			nodes.searchTipName.hide();
		};
		
		nodes.searchExampleName.addEventListener('mousedown', nameSearchHandler, false);
		nodes.searchExampleNameEng.addEventListener('mousedown', nameSearchHandler, false);
		
		this.stateSwitcher = Switcher.bind(nodes.searchTabs, nodes.searchTabs.getElementsByTagName("li"),
						[nodes.searchByName, nodes.searchByLetter, nodes.searchByIngreds]);
		
		this.stateSwitcher.onselect = function (num) {
			self.turnToState(num);
			self.controller.onStateChanged(num);
		}
		
		function changeListener (e)
		{
			nodes.searchByIngredsInput.value = ''
			self.onIngredientAdded(e.data.value)
			return false // prevents input value blinking in FF
		}
		this.completer.onconfirm = changeListener
		nodes.searchByIngredsForm.addEventListener('submit', function (e) { e.preventDefault() }, false)
	};
	
	this.turnToState = function(state){
		this.currentState = state;
		this.stateSwitcher.drawSelected(state);
		
		var viewport = nodes.mainArea.getElementsByClassName("viewport")[0]; 
		
		var bodyWrapper = nodes.bodyWrapper
		for (var k in states)
			// toggleClassName(k, states[k] == state) must be used
			states[k] == state ? bodyWrapper.addClassName(k) : bodyWrapper.removeClassName(k)
		
		if(state == states.byIngredients) {
			nodes.tagStrengthArea.show();
			this.perPage = 16;
		} else {
			nodes.tagStrengthArea.hide();
			this.perPage = 20;
		}
		
		nodes.ingredsView.hide();
		setVisible(nodes.searchTipIngredient, state == states.byIngredients)
		setVisible(nodes.searchTipName, state == states.byName)
		if(state != states.byName) $$("input", nodes.searchByName)[0].value = "";
	};
	
	this.onAllIngredientsRemoved = function () {
		this.controller.onIngredientFilter();
	};
	
	this.onIngredientAdded = function(name)
	{
		var markToken = 'марка '
		if (name.indexOf(markToken) == 0)
			this.controller.onMarkAddFilter(name.substr(markToken.length), false)
		else
			this.controller.onIngredientFilter(name, false)
	}
	
	this.onIngredientRemoved = function(name) {
		this.controller.onIngredientFilter(name, true);
	};
	
	this.onModelChanged = function(resultSet, filters, groupStates) { // model
		this.currentFilters = filters;
		
		this.renderAllPages(resultSet, filters.page);
		this.renderFilters(this.currentFilters, groupStates.tags, groupStates.strengths, groupStates.methods);
		this.controller.saveFilters(this.currentFilters);
		
		var withouts = this.searcher.withouts = {},
			ingredients = filters.ingredients;
		
		for (var i = 0, il = ingredients.length; i < il; i ++){
			withouts[ingredients[i]] = true;
		}
	};
	
	this.renderFilters = function(filters, tagState, strengthState, methodState){
		remClass(this.filterElems.letter || nodes.lettersAll, styles.selected);
		if(filters.letter != "") {
			var letterElems = $$("a", nodes.alphabetRu).concat(nodes.lettersAll);
			
			for(var i = 0; i < letterElems.length; i++) {
				if(letterElems[i].innerHTML == filters.letter.toLowerCase()){
					this.filterElems.letter = letterElems[i];
					break;
				}
			}   
		} else this.filterElems.letter = nodes.lettersAll;
		this.filterElems.letter.addClassName(styles.selected);
		
		// TODO: simplify this code with nodes[...] while avoiding the copy-paste
		var tagElems = nodes.tagsList.getElementsByTagName("dd");
		for(var i = 0; i < tagElems.length; i++) {
			var elemTxt = tagElems[i].value
			if(elemTxt == filters.tag) {
				this.filterElems.tag = tagElems[i];
				this.filterElems.tag.className = styles.selected;
			} else if(tagState.indexOf(elemTxt) == -1) {
				tagElems[i].className = styles.disabled;
			} else {
				tagElems[i].className = "";
			}
		}
		
		var strengthElems = nodes.strengthsList.getElementsByTagName("dd");
		for(var i = 0; i < strengthElems.length; i++) {
			var elemTxt = strengthElems[i].innerHTML.toLowerCase();
			if(elemTxt == filters.strength) {
				this.filterElems.strength = strengthElems[i]; 
				this.filterElems.strength.className = styles.selected;
			} else if(strengthState.indexOf(elemTxt) == -1) {
				strengthElems[i].className = styles.disabled
			} else {
				strengthElems[i].className = "";
			}
		}
		
		var methodElems = nodes.methodsList.getElementsByTagName("dd");
		for(var i = 0; i < methodElems.length; i++) {
			var elemTxt = methodElems[i].innerHTML.toLowerCase();
			if(elemTxt == filters.method) {
				this.filterElems.method = methodElems[i]; 
				this.filterElems.method.className = styles.selected;
			} else if(methodState.indexOf(elemTxt) == -1) {
				methodElems[i].className = styles.disabled
			} else {
				methodElems[i].className = "";
			}
		}
		
		var ingredientsParent = nodes.searchesList;
		ingredientsParent.empty();
		
		var words = filters.marks.concat(filters.ingredients)
		for (var i = 0, il = words.length; i < il; i++)
		{
			ingredientsParent.appendChild(this.createIngredientElement(words[i]));
			if (i != (il-1))
				ingredientsParent.appendChild(document.createTextNode(" + "));
		}
		
		if(this.currentState == states.byIngredients){
			setVisible(nodes.searchTipIngredient, words.length == 0)
			setVisible(nodes.ingredsView, words.length > 0)
		}
		
		if(filters.page > 0) {
			nodes.resultsDisplay.RollingImagesLite.goToNode($('page_'+filters.page), 'directJump');	
		}
		
		if (filters.name)
		{
			var input = $$("input", nodes.searchByName)[0]
			if (input.value != filters.name)
				input.value = filters.name
		}
	},
	
	this.renderAllPages = function(resultSet, pageNum){
		this.resultSet = resultSet;
		this.np = this.getNumOfPages(resultSet, this.perPage);
		
		nodes.resultsRoot.empty();
		
		if (resultSet.length)
			nodes.resultsDisplay.removeClassName('empty')
		else
			nodes.resultsDisplay.addClassName('empty')
			
		
		this.renderedPages = {}
		this.nodeCache     = []
		this.renderSkeleton(this.np);
		this.renderNearbyPages(pageNum);
		
		this.renderPager(this.np);
		nodes.resultsDisplay.RollingImagesLite.sync();
		nodes.resultsDisplay.RollingImagesLite.goInit();
	};
	
	this.renderSkeleton = function (count)
	{
		var parent = nodes.resultsRoot, pages = nodes.pages = []
		for (var i = 0; i < count; i++)
		{
			var page = pages[i] = document.createElement('ul')
			page.id = 'page_' + i
			page.className = 'point cocktails';
			parent.appendChild(page)
		}
	}
	
	this.renderNearbyPages = function (num, delta)
	{
		if (delta === undefined)
			delta = 1
		
		for (var i = num - delta; i <= num + delta; i++)
			if(i >= 0 && i < this.np && !this.renderedPages[i])
				this.renderPage(i)
	}
	
	this.renderGroupSet = function(parent, set){
		for(var i = 0; i < set.length; i++) {
			var dd = document.createElement("dd");
			dd.value = set[i]
			var span = document.createElement("span");
			var txt = document.createTextNode(set[i].capitalize());
			dd.appendChild(txt);
			parent.appendChild(dd);
		}		
	};
	
	this.renderLetters = function(parent, set){
		for(var i = 0; i < set.length; i++){
			var a = document.createElement("a");
			a.innerHTML = set[i];
			parent.appendChild(a);
		}
	},
	
	this.renderPage = function (num)
	{
		var cocktails = this.resultSet,
			node, cocktail, cache = this.nodeCache,
			parent = nodes.pages[num],
			end = (num + 1) * this.perPage,
			dropTargets = this.dropTargets
		
		for (var i = num * this.perPage; i < end; i++)
		{
			if (!(node = cache[i]))
			{
				if (!(cocktail = cocktails[i]))
					continue
				node = cache[i] = cocktail.getPreviewNode()
				node.img.__draggable = [cocktail.name, dropTargets]
			}
			parent.appendChild(node)
		}
		
		this.renderedPages[num] = true
	};
	
	this.createIngredientElement = function(name){
		var a = document.createElement("a");
		a.innerHTML = name;
		var self = this;
		a.addEventListener('click', function(e){
			self.onIngredientRemoved(name);
		}, false);
		return a;
	};
	
	this.getNumOfPages = function(resultSet, perPage) {
		if ((resultSet.length % perPage) == 0) return (resultSet.length/perPage);
		return parseInt(resultSet.length / perPage) + 1;
	};
	
	this.renderPager = function (numOfPages) {
		var span = nodes.pagerRoot;
		span.empty();
		for (var i = 1; i <= numOfPages; i++) {
			var a = document.createElement("a");
			a.className= i >= 10 ? "button two" : "button";
			a.appendChild(document.createTextNode(i));
			span.appendChild(a);
			span.appendChild(document.createTextNode(' '))
		}
	};
}

function keyForValue(hash, value) {
  for(var key in hash) if(hash[key] == value) return key
  return null
}

function CocktailsController (states, cookies, model, view) {
	this.model = model;
	this.view	= view;
	
	this.hashTimeout = null;
	
	this.initialize = function () {
		var filters = this.filtersFromRequest();
		var states = null;
		if(!filters) filters = this.filtersFromCookie();
		
		this.view.controller = this;
		this.model.initialize(filters);
		
		// fix for cocktails initialization issue
		this.currentHash = window.location.hash
		var me = this
		function checkHash ()
		{
			if (me.currentHash != window.location.hash)
				window.location.reload(true)
		}
		setInterval(checkHash, 250)
	};
	
	this.filtersFromRequest = function () {
		var address = window.location.href;
		var match = address.match(/.+\#(.+)/);
		if(match){
			var params = match[1].split("&");
			var filters = {};
			for(var i = 0; i < params.length; i++) {
				var pair = params[i].split("=");
				filters[pair[0]]=decodeURIComponent(pair[1]);
			}
            filters.state = states[filters.state];
			filters.page = +filters.page
			return filters;
		} else return null;
	};
	
	this.filtersFromCookie = function () {
		var cookie = Cookie.get(cookies.filter);
		if(cookie) return JSON.parse(cookie);
		else return null;
	};
	
	this.saveFilters = function (filters) {
		var self = this;
		clearTimeout(this.hashTimeout);
		this.hashTimeout = setTimeout(function() { 
			self.updatePageHash(filters);
			Cookie.set(cookies.filter, JSON.stringify(filters));
		} , 400);
	};
	
	this.updatePageHash = function(filters) {
		var pairs = [];
		for(var key in filters)
			if(filters[key] != "" || (filters[key] === 0 && key != "page")) {
				var value = filters[key];
				if(key == "state") value = keyForValue(states, value)
				pairs.push([key, value]);
			}
		
		var hash = [], encode = encodeURIComponent;
		for(var i = 0; i < pairs.length; i++) {
			hash[i] = encode(pairs[i][0]) + "=" + encode(pairs[i][1]);
		}
		if (hash)
		{
			window.location.hash = hash.join('&')
			this.currentHash = window.location.hash
		}
	};
	
	this.onLetterFilter = function(letter, all) {
		this.model.onLetterFilter(letter, all);
	};
	
	this.onTagFilter = function(tag) {
		this.model.onTagFilter(tag);
	};
	
    this.onMethodFilter = function(method) {
		this.model.onMethodFilter(method);
	};

	this.onStrengthFilter = function(strength) {
		this.model.onStrengthFilter(strength);
	};
	
	this.onIngredientFilter = function(name, remove) {
		this.model.onIngredientFilter(name, remove);
	};
	
	this.onMarkAddFilter = function(name, remove) {
		this.model.onMarkAddFilter(name, remove);
	};
	
	this.onNameFilter = function(name){
		this.model.onNameFilter(name);
	};
	
	this.onPageChanged = function(num){
		this.model.onPageChanged(num);
	};
	
	this.onStateChanged = function(num){
		this.model.onStateChanged(num);
	}
	
	this.needRandomIngredient = function(){
		return this.model.randomIngredient();
	};
	
	this.needRandomCocktailNames = function(){
		return this.model.randomCocktailNames();
	};
	
	this.initialize();
};
